package entities;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.TreeSet;

public class Product
{
	private int productId;
	private ArrayList<Rule> rulesWithProductAsAntecedent;
	private TreeSet<CustomerValue1Wrapper> customerValue1;
	
	private double aggregatedValue1;
	
	//assuming unit profit for all items
	private int pr = 1;
	
	public Product(int _productId) 
	{
		productId = _productId;
	}
	
	public void addRuleWithProductAsAntecedent(Rule rule)
	{
		if(rulesWithProductAsAntecedent == null)
			rulesWithProductAsAntecedent = new ArrayList<Rule>();
		rulesWithProductAsAntecedent.add(rule);
	}
	
	//associate products with the set of rules having the product as the antecedent
	public static void findRulesWithProductAsAntecedent(ArrayList<Rule> rules, HashMap<Integer, Product> products)
	{
		for (Rule rule : rules) 
		{
			ArrayList<Integer> antecedents = rule.getAntecedents();
			for (Integer antecedent : antecedents) 
			{
				if(products.get(antecedent) != null)
					products.get(antecedent).addRuleWithProductAsAntecedent(rule);
			}
		}
	}
	
	//calculate value 1
	public void calculateValue1(Collection<Customer> customers)
	{		
		for (Customer customer : customers) 
		{
			double v1 = 0;
			for (Rule rule : rulesWithProductAsAntecedent) 
			{
				int supports[] = customer.getSupports(rule);
				if(supports[1] != 0)
				{
					int NT = supports[0];
					double confidenceRule = NT / supports[1];
					int supportRHS = supports[2];
					v1 += NT * pr * (confidenceRule - supportRHS);
				}
			}
			insertCustomerValue1(v1, customer);
		}
	}
	
	public void insertCustomerValue1(double v1, Customer c)
	{
		if(customerValue1 == null)
			customerValue1 = new TreeSet<CustomerValue1Wrapper>(new CustomerValue1WrapperComparator());
		customerValue1.add(new CustomerValue1Wrapper(v1, c));
	}
	
	public double getUnitProfit()
	{
		return pr;
	}
	
	//calculate the aggregated value 1 for the top N customers
	public double getAggregatedValue1(int N)
	{
		double aggregatedValue1 = 0;
		
		if(customerValue1.size() < N)
		{	
			Iterator<CustomerValue1Wrapper> descendingIterator = customerValue1.descendingIterator();
			while(descendingIterator.hasNext())
			{
				aggregatedValue1 += descendingIterator.next().value1;
			}
		}
		else
		{
			int counter = 0;
			Iterator<CustomerValue1Wrapper> descendingIterator = customerValue1.descendingIterator();
			while(descendingIterator.hasNext())
			{
				aggregatedValue1 += descendingIterator.next().value1;
				counter++;
				if(counter == N)
					break;
			}
		}
		
		System.out.println("Product: " + productId + ", Aggregated Value1: " + aggregatedValue1);
		
		this.aggregatedValue1 = aggregatedValue1;
		
		return aggregatedValue1;
	}
	
	//get the top N customers for this product
	//usePaperMethod - decides what method to use to calculate added value for the customer
	//				 - true -> Use the method in paper i.e. added effect compared to threshold first and then the sum of value1() for all the products
	//				 - false -> re-rank the customers for the additional product based on the inclusive value of the item set and chose the top N customers from that
	//(commented part - naive approach - added effect is just the some of all previous selected products)
	public Collection<Customer> getTopNCustomers(int N, boolean isFirst, boolean usePaperMethod, int divideByNLHS, int divideByNTotal, int usePurchaseHistory)
	{
		ArrayList<Customer> selectedCustomers = new ArrayList<Customer>();
		if(customerValue1.size() < N)
		{		
			Iterator<CustomerValue1Wrapper> iterator = customerValue1.iterator();
			while(iterator.hasNext()) 
			{
				CustomerValue1Wrapper customerValue1Wrapper = iterator.next();
				if(customerValue1Wrapper.customer.isSelected())
					customerValue1Wrapper.customer.setSelected(true, customerValue1Wrapper.value1, productId);
				else
					customerValue1Wrapper.customer.setSelected(true, customerValue1Wrapper.value1, productId);
				
				selectedCustomers.add(customerValue1Wrapper.customer);
			}
			return selectedCustomers;
		}
		
		if(isFirst)				//first product being promoted
		{	
			int count = 0;
			//Iterator<CustomerValue1Wrapper> iterator = customerValue1.iterator();
			Iterator<CustomerValue1Wrapper> iterator = customerValue1.descendingIterator();
			while(iterator.hasNext())  
			{
				CustomerValue1Wrapper customerValue1Wrapper = iterator.next();
				if(customerValue1Wrapper.customer.isSelected())
					customerValue1Wrapper.customer.setSelected(true, customerValue1Wrapper.value1, productId);
				else
					customerValue1Wrapper.customer.setSelected(true, customerValue1Wrapper.value1, productId);
				selectedCustomers.add(customerValue1Wrapper.customer);
				if(!usePaperMethod)
					customerValue1Wrapper.customer.setInclusiveValue(customerValue1Wrapper.customer.calculateInclusiveValue(this.productId, divideByNLHS, divideByNTotal, usePurchaseHistory));
				count++;
				if(count == N)
					break;
			}
			return selectedCustomers;
		}
		else
		{
//			{ NAIVE APPROACH
//				int nrCustomersSelected = 0;
//				CustomerValue1Wrapper[] customerValue1Array = new CustomerValue1Wrapper[customerValue1.size()];
//				customerValue1.toArray(customerValue1Array);
//				
//				int firstCustomerNotChosen = customerValue1Array.length - (N + 1);
//				for(int i = customerValue1Array.length - 1; i >= customerValue1Array.length - N; i--)
//				{
//					Customer customer = customerValue1Array[i].customer;
//					if(customer.isSelected() && firstCustomerNotChosen >= 0)
//					{
//						double addedEffect = customer.getSumOfValue1OfProductsSelected();
//						
//						if(addedEffect >= customerValue1Array[firstCustomerNotChosen].value1)		//choose this customer
//						{
//							customer.setSelected(true, customerValue1Array[i].value1, productId);
//							selectedCustomers.add(customer);
//						}
//						//do not select this customer, select the firstCustomerNotChosen
//						else		
//						{
//							customer = customerValue1Array[firstCustomerNotChosen].customer;
//							customer.setSelected(true, customerValue1Array[firstCustomerNotChosen].value1, productId);
//							selectedCustomers.add(customer);
//							firstCustomerNotChosen--;
//						}
//					}
//					//select this customer
//					else
//					{
//						customer.setSelected(true, customerValue1Array[i].value1, productId);
//						selectedCustomers.add(customer);
//						nrCustomersSelected++;
//					}
//				}			
//				return selectedCustomers;
//			}
			
			if(usePaperMethod)
			{
				int nrCustomersSelected = 0;
				
				CustomerValue1Wrapper[] customerValue1Array = new CustomerValue1Wrapper[customerValue1.size()];
				customerValue1.toArray(customerValue1Array);
				
				int firstCustomerNotChosen = customerValue1Array.length - (N + 1);
				for(int i = customerValue1Array.length - 1; i >= customerValue1Array.length - N; i--)
				{
					Customer customer = customerValue1Array[i].customer;
					if(customer.isSelected() && firstCustomerNotChosen >= 0)
					{
						//check the added effect of choosing this customer for this product as well
						double addedEffect = customer.getAddedValueDueToProduct(productId);					
						
						if(addedEffect > 0.5)	//do not select this customer, select the firstCustomerNotChosen
						{
							customer = customerValue1Array[firstCustomerNotChosen].customer;
							customer.setSelected(true, customerValue1Array[firstCustomerNotChosen].value1, productId);
							selectedCustomers.add(customer);
							firstCustomerNotChosen--;
						}
						else
						{
							double averageValue1TillNow = customer.getAverageValuee1SelectedForProducts(customerValue1Array[i].value1);
							//select this customer
							if(averageValue1TillNow * addedEffect > customerValue1Array[firstCustomerNotChosen].value1)		//choose this customer
							{
								customer.setSelected(true, customerValue1Array[i].value1, productId);
								selectedCustomers.add(customer);
							}
							//do not select this customer, select the firstCustomerNotChosen
							else		
							{
								customer = customerValue1Array[firstCustomerNotChosen].customer;
								customer.setSelected(true, customerValue1Array[firstCustomerNotChosen].value1, productId);
								selectedCustomers.add(customer);
								firstCustomerNotChosen--;
							}
						}
					}
					//select this customer
					else
					{
						customer.setSelected(true, customerValue1Array[i].value1, productId);
						selectedCustomers.add(customer);
						nrCustomersSelected++;
					}
				}			
				return selectedCustomers;
			}
			else
			{
				TreeSet<CustomerValue1Wrapper> tempCustomerValue = new TreeSet<CustomerValue1Wrapper>(new CustomerValue1WrapperComparator());				
								
				for(Iterator<CustomerValue1Wrapper> iterator = customerValue1.descendingIterator(); iterator.hasNext();) 
				{
					CustomerValue1Wrapper cvw = (CustomerValue1Wrapper)iterator.next();
					double differenceInclusiveValue = cvw.customer.calculateInclusiveValue(this.productId, divideByNLHS, divideByNTotal, usePurchaseHistory) - cvw.customer.getInclusiveValue();
					tempCustomerValue.add(new CustomerValue1Wrapper(differenceInclusiveValue, cvw.customer, cvw.value1));
				}
								
				for(Iterator<CustomerValue1Wrapper> iterator = tempCustomerValue.descendingIterator(); iterator.hasNext();)
				{
					if(selectedCustomers.size() < N)
					{
						CustomerValue1Wrapper cvw = iterator.next();
						cvw.customer.setSelected(true, cvw.origValue1, this.productId);
						cvw.customer.setInclusiveValue(cvw.customer.getInclusiveValue() + cvw.value1);
						selectedCustomers.add(cvw.customer);
					}
					else
						break;
				}
				
				return selectedCustomers;
			}
		}
	}
	
	public int getProductId()
	{
		return productId;
	}

	public double getAggregatedValue1() 
	{
		return aggregatedValue1;
	}
	
	public ArrayList<Rule> getRulesWithProductAsAntecedent() 
	{
		return rulesWithProductAsAntecedent;
	}
	
	private class CustomerValue1Wrapper
	{
		double value1;
		Customer customer;
		
		//used to store value1 when value1 stores the inclusive value
		double origValue1;
		
		public CustomerValue1Wrapper(double v1, Customer c) 
		{
			value1 = v1;
			customer = c;
		}
		
		public CustomerValue1Wrapper(double v1, Customer c, double origV1) 
		{
			value1 = v1;
			customer = c;
			origValue1 = origV1;
		}
	}
	
	private class CustomerValue1WrapperComparator implements Comparator<CustomerValue1Wrapper>
	{
		public int compare(CustomerValue1Wrapper c1, CustomerValue1Wrapper c2)
		{
			if(c1.value1 <= c2.value1)
				return -1;
			else
				return 1;
		}
		
	}
}

